﻿using Com.Lancetinc.Evs.Data;
using Com.Lancetinc.Evs.Events;
using Com.Lancetinc.Evs.Helpers;
using Com.Lancetinc.Evs.Services;
using NLog;
using System;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;

namespace Com.Lancetinc.Evs.Controls
{
    /// <summary>
    /// 录像中
    /// </summary>
    public partial class VideoPlayUserControl : IVideoUserControl
    {
        private static readonly ILogger logger = LogManager.GetCurrentClassLogger();

        private static readonly long HALF_TIMER_INTERVAL_MILLS = 10;

        private static readonly long PAUSE_PLAY_MILLS = HALF_TIMER_INTERVAL_MILLS * 3;

        public int VideoMinWidth { get; set; } = 400;
        
        private readonly DispatcherTimer mDispatcherTimer;

        private ExperInfo mExper;

        private long mRequiredMediaPositionMills;

        private bool sliderDrag;
        private bool sliderMediaChange;

        public VideoPlayUserControl()
        {
            VideoRotationAngle = 0;

            InitializeComponent();

            mediaElement.MediaFailed += mediaElement_MediaFailed;
            mediaElement.MediaEnded += mediaElement_MediaEnded;
            mediaElement.MediaOpened += mediaElement_MediaOpened;

            mDispatcherTimer = new DispatcherTimer
            {
                Interval = TimeSpan.FromMilliseconds(HALF_TIMER_INTERVAL_MILLS * 2)
            };
            mDispatcherTimer.Tick += DispatcherTimer_Tick;
        }

        public long PlayedMillSeconds => (long)mediaElement.Position.TotalMilliseconds;

        public event EventHandler<VideoStateChangeEventArgs> VideoStateChanged;

        public void Pause()
        {
            mediaElement.Pause();
            OnVideoStateChanged(VideoStateEnum.Paused);
        }

        public Task<bool> Play()
        {
            VideoScaleX = mExper.Exper.Exper.FlipHorizontally == 1 ? -1 : 1;
            VideoScaleY = mExper.Exper.Exper.FlipVertically == 1 ? -1 : 1;

            if (mediaElement.Source == null)
            {
                SetVideoContainerSize();
                mediaElement.Source = new Uri(mExper.VideoPath);
                VideoRotationAngle = mExper.Exper.Exper.VideoRotationAngle;
            }

            mRequiredMediaPositionMills = 0;
            mediaElement.Play();
            OnVideoStateChanged(VideoStateEnum.Playing);
            return Task.FromResult(true);
        }

        public void CaptureImage(string saveFileName)
        {
            MediaElementBaseHelper.CaptureImage(mediaElement, saveFileName, mExper.Exper.Exper.VideoWidth);
        }

        public void Stop(RequireStopReason reason)
        {
            if (mediaElement.Source != null)
            {
                mediaElement.Close();
                mediaElement.Source = null;
                OnVideoStateChanged(VideoStateEnum.Stop);
            }
        }

        private void DispatcherTimer_Tick(object sender, EventArgs e)
        {
            // 用户拖动滚动条
            if (mRequiredMediaPositionMills > 0)
            {
                lock (slider)
                {
                    var delta = mediaElement.Position.TotalMilliseconds - mRequiredMediaPositionMills;
                    if (delta > -HALF_TIMER_INTERVAL_MILLS)
                    {
                        mediaElement.Pause();
                        mRequiredMediaPositionMills = 0;
                    }
                    else
                    {
                        return;
                    }
                }
            }

            CtrlVideoTime.VideoMillSeconds = (long)mediaElement.Position.TotalMilliseconds;

            if (VideoState == VideoStateEnum.Paused)
            {
                return;
            }

            if (!sliderDrag)
            {
                ChangeSlideValue();
            }
        }

        private void mediaElement_MediaFailed(object sender, ExceptionRoutedEventArgs e)
        {
            Dispatcher.BeginInvoke(new Action(() =>
            {
                logger.Error(e.ErrorException);
                OnVideoStateChanged(VideoStateEnum.Stop);
                MessageHelper.ShowError(e.ErrorException.Message);
            }));
        }

        private void mediaElement_MediaOpened(object sender, RoutedEventArgs e)
        {
            Dispatcher.BeginInvoke(new Action(() =>
            {
                var mediaDuration = (long)mediaElement.NaturalDuration.TimeSpan.TotalMilliseconds;
                slider.Value = 0;
                slider.Maximum = mediaDuration;
                OnVideoStateChanged(VideoStateEnum.Playing);

                try
                {
                    var exper = mExper.Exper.Exper;
                    if (exper.MediaDuration < mediaDuration)
                    {
                        exper.MediaDuration = mediaDuration;
                        AppRepositories.Exper.Save(exper);
                    }
                }
                catch (Exception ex)
                {
                    logger.Error(ex.Message);
                }
            }));
        }

        private void mediaElement_MediaEnded(object sender, RoutedEventArgs e)
        {
            mediaElement.Close();
            mediaElement.Source = null;
            Dispatcher.BeginInvoke(new Action(() =>
            {
                slider.Value = 0;
                OnVideoStateChanged(VideoStateEnum.Stop);

                CtrlVideoTime.VideoMillSeconds = 0;
            }));
        }

        private void ChangeMediaPosition()
        {
            if (sliderMediaChange)
            {
                return;
            }

            sliderDrag = true;
            try
            {
                if (VideoState == VideoStateEnum.Paused)
                {
                    lock (slider)
                    {
                        var isPlaying = mRequiredMediaPositionMills > 0;
                        mRequiredMediaPositionMills = (long)slider.Value;
                        mediaElement.Position =
                            TimeSpan.FromMilliseconds(mRequiredMediaPositionMills - PAUSE_PLAY_MILLS);
                        if (!isPlaying)
                        {
                            mediaElement.Play();
                        }
                    }
                }
                else
                {
                    mediaElement.Position = TimeSpan.FromMilliseconds(slider.Value);
                }
            }
            finally
            {
                sliderDrag = false;
            }
        }

        private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            RefreshVideoTime();

            if (sliderMediaChange)
            {
                return;
            }

            Dispatcher.BeginInvoke(new Action(ChangeMediaPosition), null);
        }

        private void ChangeSlideValue()
        {
            if (sliderDrag)
            {
                return;
            }

            sliderMediaChange = true;
            try
            {
                slider.Value = mediaElement.Position.TotalMilliseconds;
            }
            finally
            {
                sliderMediaChange = false;
            }
        }

        private void OnVideoStateChanged(VideoStateEnum videoState)
        {
            VideoState = videoState;
            VideoStateChanged.Invoke(this, new VideoStateChangeEventArgs(videoState));
            slider.IsEnabled = videoState != VideoStateEnum.Stop;
            mDispatcherTimer.IsEnabled = videoState == VideoStateEnum.Playing || videoState == VideoStateEnum.Paused;
            if (videoState == VideoStateEnum.Playing)
            {
                PanelTime.Visibility = Visibility.Visible;
            }
            else if (videoState == VideoStateEnum.Stop)
            {
                PanelTime.Visibility = Visibility.Collapsed;
            }
        }

        public void SetExper(ExperInfo exper)
        {
            mediaElement.Source = null;
            mExper = exper;
            CtrlVideoTime.Action = VideoTimeDisplayAction.Playing;
            CtrlVideoTime.ExperBeginTime = exper.GetStartTime();
            SetVideoContainerSize();
        }

        private void SetVideoContainerSize()
        {
            if (mExper == null)
            {
                return;
            }

            var expectedMediaSize = CalcExpectedMediaSize();
            DoSetVideoContainerSize(expectedMediaSize);
        }

        private void DoSetVideoContainerSize(Size expectedSize)
        {
            var maxWidth = ActualWidth;
            var maxHeight = ActualHeight - slider.ActualHeight - CtrlVideoTime.ActualHeight;

            var actualWidth = Math.Min(maxWidth, expectedSize.Width);
            var actualHeight = Math.Min(maxHeight, expectedSize.Height);

            var actualWH = actualWidth / actualHeight;
            var expectedWH = expectedSize.Width / expectedSize.Height;

            if (actualWH > expectedWH)
            {
                actualWidth = actualHeight * expectedWH;
            }
            else if (actualWH < expectedWH)
            {
                actualHeight = actualWidth / expectedWH;
            }

            mediaElement.Width = actualWidth;
            mediaElement.Height = actualHeight;

            CtrlVideoTime.Width = Math.Max(260, actualWidth);
        }

        private Size CalcExpectedMediaSize()
        {
            var scale = IsFullScreen ? 2 : 1;
            var w = mExper.Exper.Exper.VideoWidth * scale;
            var h = mExper.Exper.Exper.VideoHeight * scale;
            var videoMinWidth = scale * VideoMinWidth;

            // 限制最小尺寸
            if (w < videoMinWidth)
            {
                var ratio = h / w;
                w = videoMinWidth;
                h = ratio * w;
            }
            return new Size(w, h);
        }

        private void UserControl_Unloaded(object sender, RoutedEventArgs e)
        {
            mDispatcherTimer.Stop();
            mediaElement.Close();
        }

        #region IsFullScreen

        public static readonly DependencyProperty IsFullScreenProperty =
            DependencyProperty.Register(nameof(IsFullScreen), typeof(bool), typeof(VideoPlayUserControl));

        public bool IsFullScreen
        {
            get => (bool)GetValue(IsFullScreenProperty);
            set
            {
                SetValue(IsFullScreenProperty, value);
                ImgLogo.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
                SetVideoContainerSize();
            }
        }

        #endregion

        #region VideoState

        private VideoStateEnum mVideoState = VideoStateEnum.Stop;

        public VideoStateEnum VideoState
        {
            get
            {
                if (mVideoState == VideoStateEnum.Playing)
                {
                    return mVideoState;
                }

                return mVideoState;
            }
            private set => mVideoState = value;
        }

        #endregion

        #region VideoRotationAngle

        public static readonly DependencyProperty VideoRotationAngleProperty =
            DependencyProperty.Register("VideoRotationAngle", typeof(double), typeof(VideoPlayUserControl));

        public double VideoRotationAngle
        {
            get => (double)GetValue(VideoRotationAngleProperty);
            set => SetValue(VideoRotationAngleProperty, value);
        }

        #endregion

        #region VideoScaleX

        public static readonly DependencyProperty VideoScaleXProperty =
            DependencyProperty.Register(nameof(VideoScaleX), typeof(double), typeof(VideoPlayUserControl), new PropertyMetadata(1D));

        public double VideoScaleX
        {
            get => (double)GetValue(VideoScaleXProperty);
            set => SetValue(VideoScaleXProperty, value);
        }

        #endregion

        #region VideoScaleY

        public static readonly DependencyProperty VideoScaleYProperty =
            DependencyProperty.Register(nameof(VideoScaleY), typeof(double), typeof(VideoPlayUserControl), new PropertyMetadata(1D));

        public double VideoScaleY
        {
            get => (double)GetValue(VideoScaleYProperty);
            set => SetValue(VideoScaleYProperty, value);
        }

        #endregion

        private void RefreshVideoTime()
        {
            LblTimeSpan.Text = FormatTime(mediaElement.Position);
            if (mediaElement.NaturalDuration == Duration.Automatic)
                return;
            LblTotalTime.Text = FormatTime(mediaElement.NaturalDuration.TimeSpan);
        }

        private string FormatTime(TimeSpan ts)
        {
            var seconds = (int)ts.TotalSeconds;
            var sb = new StringBuilder();
            // 时
            if (seconds >= 3600)
            {
                sb.AppendFormat("{0:00}", seconds / 3600).Append(":");
                seconds %= 3600;
            }

            // 分
            sb.AppendFormat("{0:00}", seconds / 60).Append(":");
            seconds %= 60;
            // 秒
            sb.AppendFormat("{0:00}", seconds);
            return sb.ToString();
        }
    }
}